﻿class Snake
{
	
	//current bugs -> 1 if snake exceeds maximum grey body sessions displace black ones 
	//current bugs -> 2 if sequence is correct then keeps adding grey body sections regardless of sequence.
	//current bugs -> 3 collision detection fails on all black tail - updating of object number is buggy
						//fixed
	//current hacks -> 1 snake objects are placed on get_next_highest_depth().//fixed
	
	//tail 
	private var my_max_length:Number = 0; // stores maximum length value for the snake
	private var my_buffer:Number = 0;
	private var tail_pos:Array; //to store the projected positions of the units (used for adding tail units)
	private var tail_trail:Array; // to store the delayed movement of the snake
	private var BASE_DEPTH:Number;
	private var name_inc:Number = 0; // an incremental value for naming objects dynamically
	
	//pointers
	private var my_head:MovieClip = null; // to store the head unit for the Snake (remember the class is instantiated by the movieclip on stage)
	private var my_p_root:MovieClip = null; // stores pointer to root where snakes objects are instantiated.
	private var my_p_head:MovieClip = null; // store a forward reference to the head object
	private var my_p_game:Object = null; // store a reference to the game
	private var my_p_world:Object = null; // store a reference to the world
	private var tail_one:Array; //stores pointers to musical objects - this is also used as a sequence array because each body object has a sound attribute.
	private var tail_two:Array;//stores pointers to non musical objects
	private var tail_all:Array;//used to address all objects in the tail
	
	//synchro flags
	private var cue_flag:Number = 0; //0 is false; 1(+) is true; 2 flags removal
	private var check_flag:Boolean = false; // boolean for preventing duplicate sequence checks on a downbeat
	
	public function Snake(p_root:MovieClip, p_world:Object, p_game:Object, max_length:Number, buffer_length:Number)
	{
		//trace ("new Snake created"); // debug
		
		my_max_length = max_length; //initialise maximum snake length
		my_buffer = buffer_length
		
		tail_one = new Array(); //re-initialise tail array
		tail_two = new Array();
		tail_all = new Array();
		push_sequence();//overwrite the music sequence at root with the empty sequence
		
		//tail_trail = new Array(my_max_length); //initialise the array to the maximum length of the snake tail - this allows for a delay trail of movements to be captured and interpreted by tail elements
		//tail_pos = new Array(my_max_length+1); //initialise the array to the maximum length of the snake tail (+1) - this allows for a delay trail of positions for adding sections successfully +1 is for the 20th element of the snake to refer to.
		
		tail_trail = new Array(my_buffer); //initialise the array to the maximum length of the snake tail - this allows for a delay trail of movements to be captured and interpreted by tail elements
		tail_pos = new Array(my_buffer+1); //initialise the array to the maximum length of the snake tail (+1) - this allows for a delay trail of positions for adding sections successfully +1 is for the 20th element of the snake to refer to.
		
		my_p_root = p_root; //initialise pointer to root.
		my_p_game = p_game;
		my_p_head = my_p_root.attachMovie("Head","my_head",2000,{unit_size:20, my_p_snake:this,my_p_world:p_world}); //instantiate a head object and by consequence Head and Object Class.
		my_p_world = p_world;
		
		BASE_DEPTH = 1000;
		
		
	}
	public function update_snake(Void):Void //do all the things the snake does on each step
	{
		move_snake();
		check_collision(); // -> die or eat
		show_beat();
		sync_test(); //ambiguously named function for testing the sequence on the first beat
	}
	public function clear_snake(Void):Void
	{
		my_p_head.removeMovieClip(); //kill the head
		for (var i:Number = 0; i<tail_all.length; i++)//kill the body
		{
			tail_all[i].removeMovieClip();
		}
	}
	public function get_tail_array(Void):Array
	{
		var tail_copy:Array = tail_all.slice();//look to all objects
		return (tail_copy);
	}
	private function sync_test(Void):Void //sync test ensures that the current loop plays once before checking
	{	
		if(my_p_root.stp == 0 && cue_flag && !check_flag)
		{
			if(cue_flag==1) //if an event has been flagged
			{
				check_flag = true;//flag that this is occuring to prevent retriggers
				cue_flag++//increment the flag to say that the cue is complete
				//trace("phrase playing");
				//trace("check flag: " + check_flag + " cue_flag: " + cue_flag);
			}
			else if (cue_flag>1)//if the que is complete
			{
				check_seq(); //check the sequence for a valid combination
				cue_flag = 0; //reset the que flag
				//trace("check sequence");
				//trace("check flag: " + check_flag + " cue_flag: " + cue_flag);
			}
		}
		
		if(check_flag && my_p_root.stp > 0)
		{
			check_flag = false;
			//trace("reset checking flag");
			//trace("check flag: " + check_flag + " cue_flag: " + cue_flag);
		}
	}
	private function check_seq(Void):Void //could be optimised to check index against eaten object
	{
		//trace("check seq called");
		var mismatch:Boolean = false
		var TAIL_ONE_LENGTH:Number = tail_one.length; //snapshot this value to constant since move tail will alter array length
		for (var i:Number = 0; i < TAIL_ONE_LENGTH; i++) //check each tail object sound to see if it matches the sequence
		{
			//optimise
			if (!mismatch && tail_one[i].sound != my_p_world.target_sequence[i])
			{
				//trace("incorrect sequence: " + tail_one[i].sound + " -> " + my_p_world.target_sequence[i]);
				mismatch = true; //flag that the sequence is incorrect
				i=0; //reset the search loop in case other sections need to be moved too
			}
			if (mismatch)//the loop has started again at 0...
			{
				//turn the body section black
				tail_one[0].gotoAndStop(3);
				//update tails
				move_to_tail_two(0);
			}
		}
		if(mismatch)//this is outside the main loop so the sound only plays once, rather than for each mismatch found
		{
			//beta
			my_p_root.incorrect.start(); //play incorrect sound effect
			my_p_game.my_overlay.incorrect(); //update the on screen display
		}
		else if(!mismatch)
		{
			my_p_root.correct.start(); //play correct sound effect
			my_p_game.my_overlay.correct(); //update the on screen display
		}
		if(!mismatch && TAIL_ONE_LENGTH >= my_p_root.len)
		{
			trace ("sequence correct!");
			my_p_game.win();
		}
	}
	private function move_snake(Void):Void //move the snake one unit in any direction
	{
		var position:Object = new Object; //next capture position
		position.x = my_p_head._x;
		position.y = my_p_head._y;
		
		var dir:String = my_p_head.move_head_unit(); //move head one unit after capture (1 step ahead)
		
		update_tail_pos(position);
		update_tail_one(); //move the tail according to the trail values.
		update_tail_two(); //move the second tail according to absolute values(variable by 1st tail length)
		update_trail(dir); //update the trail values for the snake tail with last move direction
	}
	private function update_trail(dir:String):Void
	{
		tail_trail.unshift(dir); // add new element to beginning
		tail_trail.pop(); // remove element from end - remember this circular buffer is *always* 20 elements long
	}
	private function update_tail_pos(position:Object):Void
	{
		tail_pos.unshift(position); // add new element to beginning
		tail_pos.pop(); // remove element from end - remember this circular buffer is *always* 20 elements long
	}
	private function update_tail_one(Void):Void //move all the tail elements according to the relevent delayed value
	{		
		for (var i=0; i<tail_one.length; i++)
		{
			tail_one[i].move_unit(tail_trail[i]);
		}
	}
	private function update_tail_two(Void):Void
	{
		
		//tail two is moved by absolute value according to the tail_pos buffer
		var offset:Number = tail_one.length;
		for (var i:Number = 0; i<tail_two.length; i++)
		{
			tail_two[i]._x = tail_pos[offset].x;
			tail_two[i]._y = tail_pos[offset].y;
			offset++;
		}
	}
	private function update_tail_all(Void):Void
	{
		tail_all = tail_one.concat(tail_two);
		//trace(tail_all);
	}
	private function show_beat(Void):Void
	{
		for (var i=0; i<tail_one.length; i++)
		{
			//tail[i]._alpha = 100; // change this indicator for something less cpu intensive...
			tail_one[i].gotoAndStop(1); //changing frames is much cheaper than alpha blends
		}
		//tail[my_p_root.stp]._alpha = 50;
		tail_one[my_p_root.stp].gotoAndStop(2);
	}
	private function add_to_tail_one(sound:String):Void
	{
		if (tail_one.length < my_max_length) //the array index controls the level since this is linear (nothing needs recycling) BASE_DEPTH+tail.length
		{
			// instantiate a body segment and add it's reference to the array of segments...
			// This needs some work 
			tail_one.push(my_p_root.attachMovie("Body","my_body"+name_inc++,BASE_DEPTH+name_inc++,{unit_size:20,sound:sound,_x:tail_pos[tail_one.length].x,_y:tail_pos[tail_one.length].y}));
			
			update_tail_all();
		push_sequence();//update the sound sequence to include new tail element.
		
		//use this code if you don't want the checking period to be expanded 
		/*
		if (cue_flag < 1)//if a check event hasn't already been flagged 
		{
			cue_flag = 1;//flag a check event
			my_p_game.my_overlay.checking(); //update the on screen display
		}
		*/
		
		//use this code if you *do* want the checking period to be expanded 
		cue_flag = 1;//flag a check event
		my_p_game.my_overlay.checking(); //update the on screen display
		
		//trace("check cued");
		//trace("check flag: " + check_flag + " cue_flag: " + cue_flag);
		}
		else if (tail_one.length >= my_max_length)
		{
			trace ("max tail exceeded");
			//if food makes body too long push it into the second tail
			add_to_tail_two(my_p_root.attachMovie("Body","my_body"+name_inc++,BASE_DEPTH+name_inc++,{unit_size:20,sound:sound,_x:tail_pos[tail_one.length].x,_y:tail_pos[tail_one.length].y}));
		}
	}
	private function move_to_tail_two(index):Void
	{
		var body_object:MovieClip = tail_one[index]; // point to the object
		tail_one.splice(index,1); //remove it from the tail one array
		tail_two.push(body_object);//push it into the second tail
		update_tail_all();
		push_sequence();
	}
	private function add_to_tail_two(food:MovieClip):Void//add directly to tail two
	{
		food.gotoAndStop(3); //turn black
		tail_two.push(food);//push it into the second tail
		update_tail_all();
	}
	private function push_sequence(Void):Void
	{
		//trace(my_p_root.sequence.length); // debug scope
		var sound_sequence:Array = new Array();
		for (var i:Number = 0; i<tail_one.length; i++)
		{
			sound_sequence[i] = tail_one[i].sound; //pour sounds into a new array
		}
		//trace (sound_sequence);
		my_p_root.seq = sound_sequence.slice(); //copy array over sequence array
	}
	private function check_collision()
	{
		my_p_head.check_collision()
	}
	private function eat(food_object:MovieClip)
	{
		add_to_tail_one(food_object.object_sound);
		food_object.is_eaten = true; //implicit setter
	}
	private function die(Void):Void
	{
		my_p_game.death();
	}
}